/*!
\copyright  Copyright (c) 2020-2025 Qualcomm Technologies International, Ltd.
            All Rights Reserved.
            Qualcomm Technologies International, Ltd. Confidential and Proprietary.
\version    
\file        fast_pair_advertising.c
\brief      Handles Fast Pair Advertising Data
*/

/*! Firmware and Library Headers */
#include <source.h>
#include <sink.h>
#include <stdint.h>
#include <util.h>
#include <stdlib.h>
#include <message.h>
#include <logging.h>
#include <panic.h>
#include <stdio.h>
#include <connection_manager.h>
#include <connection.h>
#include <string.h>
#include <cryptovm.h>

/*! Application Headers */
#include "le_advertising_manager.h"
#include "fast_pair_advertising.h"
#include "fast_pair_bloom_filter.h"
#include "fast_pair_config.h"
#include "tx_power.h"
#include "fast_pair_battery_notifications.h"
#include "user_accounts.h"
#include "fast_pair_adv_sass.h"
#include "gatt_cas_server_uuids.h"

#define DEBUG_LOG_FP_ADVERTISING        DEBUG_LOG
#define DEBUG_LOG_FP_ADVERTISING_DATA   DEBUG_LOG_DATA

/*! \brief Global data structure for fastpair adverts */
typedef struct
{
    /*! The fast pair advertising module task */
    TaskData task;
    le_adv_item_handle adv_register_handle;
#if (!USE_EXTENDED_ADVERTS && SUPPORT_BLE_DEVICE_EXTENSION)
    /* When using legacy adverts CAS service is sent through scan response */
    le_adv_item_handle cas_register_handle;
#endif
    bdaddr  adv_bdaddr;
    uint8   *account_key_filter_adv_data;
    bool    identifiable;
    bool    in_use_account_key_active;
} fastpair_advert_data_t;

/*! Global Instance of fastpair advertising data */
fastpair_advert_data_t fastpair_advert;

typedef enum
{
    FAST_PAIR_ADVERTISING_INTERNAL_RETAIN_RPA,
} fast_pair_advertising_internal_msgs_t;

#define FAST_PAIR_ADVERTISING_RETAIN_RPA_TIMEOUT_MS    (D_SEC(120))

/*! Module Constants */
#define FAST_PAIR_GFPS_IDENTIFIER 0xFE2C

#define FAST_PAIR_ADV_ITEM_MODEL_ID 0 /*During BR/EDR Connectable and Discoverable*/
#define FAST_PAIR_ADV_ITEM_BLOOM_FILTER_ID 0 /*During BR/EDR Connectable and Non-Discoverable*/

/*GFPS Identifier, Model ID in Identifiable mode and GFPS Identifier, Hashed Account key (Bloom Filter) including Salt in Unidentifiable mode*/
/*Note transmit power needed for fastpair adverts will go as part of Tx_power module*/
#define FAST_PAIR_AD_ITEMS_IDENTIFIABLE 1
#define FAST_PAIR_AD_ITEMS_UNIDENTIFIABLE_WITH_ACCOUNT_KEYS 1

/* Adv interval when BR/EDR is discoverable should be <=100ms*/
#define FP_ADV_INTERVAL_IDENTIFIABLE_MIN     90
#define FP_ADV_INTERVAL_IDENTIFIABLE_MAX     100

/*FP when BR/EDR non-discoverable/Silent Pairing*/
#define FP_ADV_INTERVAL_UNIDENTIFIABLE_MIN   225
#define FP_ADV_INTERVAL_UNIDENTIFIABLE_MAX   250

/*! Const fastpair advertising data in Length, Tag, Value format*/
#define FP_SIZE_AD_TYPE_FIELD 1 
#define FP_SIZE_LENGTH_FIELD 1

typedef struct
{
    bool specify_max_tx_power;
    int8 max_tx_power;
} fastpair_tx_power_t;

static fastpair_tx_power_t fp_adv_param_tx_power;

/* TX Power advertising data */
#if SPECIFY_TX_POWER
#define FP_TX_POWER_DATA_SIZE (3)
#else 
#define FP_TX_POWER_DATA_SIZE (0)
#endif

/* Length of CAS (Common Audio Service) UUID advert data item - when it is sent through scan response */
#define FP_CAS_ADV_ITEM_DATA_SIZE 4

/* Length of CAS UUID advert data - when it is sent through an extended advert */
#if (USE_EXTENDED_ADVERTS && SUPPORT_BLE_DEVICE_EXTENSION)
#define FP_CAS_DATA_SIZE 4
#else
#define FP_CAS_DATA_SIZE 0
#endif
#if SUPPORT_BLE_DEVICE_EXTENSION
static uint8_t fp_cas_data[FP_CAS_ADV_ITEM_DATA_SIZE] = {
    FP_CAS_ADV_ITEM_DATA_SIZE - 1,
    ble_ad_type_service_data,
    UUID_COMMON_AUDIO_SERVICE & 0xFF,
    UUID_COMMON_AUDIO_SERVICE >> 8
};
#endif
#if (!USE_EXTENDED_ADVERTS && SUPPORT_BLE_DEVICE_EXTENSION)
static const le_adv_item_data_t fp_cas_data_item = {
    .data = fp_cas_data,
    .size = FP_CAS_ADV_ITEM_DATA_SIZE
};
#endif

/* Flags in Advertising Payload when in non-discoverable mode(All bits are reserved for future use)*/
#define FP_ADV_PAYLOAD_FLAGS 0x00
/* Account Key data when in non-discoverable mode and no acocunt keys are present*/
#define FP_ADV_PAYLOAD_ACCOUNT_KEY_DATA 0x00
#define SIZE_GFPS_ID 2

/* Size of model id advert payload */
#define SIZE_MODEL_ID 3
/* Size of account data advert payload */
#define SIZE_ACCOUNT_DATA 2
/*! Const fastpair advertising data in Length, Tag, Value format*/
/* Length of account key data when non-discoverable and no account keys are present*/
#define SIZE_GFPS_ID_ADV (FP_SIZE_LENGTH_FIELD+FP_SIZE_AD_TYPE_FIELD+SIZE_GFPS_ID)
/* Size of model id advert (discoverable) */
#define SIZE_MODEL_ID_ADV (FP_SIZE_LENGTH_FIELD+FP_SIZE_AD_TYPE_FIELD+SIZE_MODEL_ID + SIZE_GFPS_ID)
/* Size of account data advert when no account keys are present (non-discoverable) */
#define SIZE_ACCOUNT_DATA_ADV (SIZE_GFPS_ID_ADV+SIZE_ACCOUNT_DATA)

/* Size of static advertising data item when discoverable */
#define SIZE_DISC_ADV_ITEM_DATA (SIZE_MODEL_ID_ADV+FP_TX_POWER_DATA_SIZE+FP_CAS_DATA_SIZE)
/* Size of static advertising data item when non-discoverable */
#define SIZE_NON_DISC_ADV_ITEM_DATA (SIZE_ACCOUNT_DATA_ADV + FP_TX_POWER_DATA_SIZE + FP_CAS_DATA_SIZE)

typedef struct {
    uint8_t account_data[SIZE_ACCOUNT_DATA_ADV];
#if SPECIFY_TX_POWER
    uint8_t tx_power_data[FP_TX_POWER_DATA_SIZE];
#endif
#if (USE_EXTENDED_ADVERTS && SUPPORT_BLE_DEVICE_EXTENSION)
    uint8_t cas_data[FP_CAS_DATA_SIZE];
#endif
} non_disc_adv_data_t;

static non_disc_adv_data_t fp_non_disc_adv_data;
static const le_adv_item_data_t fp_non_disc_adv =
{
    .size = SIZE_NON_DISC_ADV_ITEM_DATA,
    .data = (uint8_t *)&fp_non_disc_adv_data
};

typedef struct {
    uint8_t model_id_data[SIZE_MODEL_ID_ADV];
#if SPECIFY_TX_POWER
    uint8_t tx_power_data[FP_TX_POWER_DATA_SIZE];
#endif
#if (USE_EXTENDED_ADVERTS && SUPPORT_BLE_DEVICE_EXTENSION)
    uint8_t cas_data[FP_CAS_DATA_SIZE];
#endif
} disc_adv_data_t;

static disc_adv_data_t fp_disc_adv_data;
static le_adv_item_data_t fp_disc_adv =
{
    .size = SIZE_DISC_ADV_ITEM_DATA,
    .data = (uint8_t*)&fp_disc_adv_data
};

static bool IsHandsetConnAllowed = FALSE;

#if SPECIFY_TX_POWER
static void fastPair_SetAdvTxPowerData(uint8_t* adv_data, int8_t tx_power)
{
    adv_data[0] = FP_TX_POWER_DATA_SIZE - 1 ;
    adv_data[1] = (uint8_t)ble_ad_type_tx_power_level;
    adv_data[2] = (uint8_t)tx_power; 
}
#endif

#if (USE_EXTENDED_ADVERTS && SUPPORT_BLE_DEVICE_EXTENSION)
static void fastPair_SetAdvCasData(uint8_t* adv_data)
{
    adv_data[0] = fp_cas_data[0];
    adv_data[1] = fp_cas_data[1];
    adv_data[2] = fp_cas_data[2];
    adv_data[3] = fp_cas_data[3];
}
#endif

/*! \brief Function to get the tx power in the advertising packet. */
static int8_t fastPair_GetTxPower(void)
{
#if SPECIFY_TX_POWER
    int8_t tx_power;
    if(fastpair_advert.adv_register_handle && LeAdvertisingManager_GetTxPowerForAdvertisingItem(fastpair_advert.adv_register_handle, &tx_power))
    {
        tx_power += TxPower_GetTxPowerPathLoss();
    }
    else
    {
        tx_power =  TxPower_LEGetData();
    }

    return tx_power;
#endif
}

static void fastPair_InitDiscoverableAdvData(void)
{
    uint32 fp_model_id;

    fp_model_id = fastPair_GetModelId();
    DEBUG_LOG_FP_ADVERTISING("fastPair_ModelIdAdvData %04x", fp_model_id);
    size_t i = 0;
    fp_disc_adv_data.model_id_data[i++] = SIZE_MODEL_ID_ADV - 1;
    fp_disc_adv_data.model_id_data[i++] = ble_ad_type_service_data;
    fp_disc_adv_data.model_id_data[i++] = FAST_PAIR_GFPS_IDENTIFIER & 0xFF;
    fp_disc_adv_data.model_id_data[i++] = (FAST_PAIR_GFPS_IDENTIFIER >> 8) & 0xFF;
    fp_disc_adv_data.model_id_data[i++] = (fp_model_id >> 16) & 0xFF;
    fp_disc_adv_data.model_id_data[i++] = (fp_model_id >> 8) & 0xFF;
    fp_disc_adv_data.model_id_data[i++] = fp_model_id & 0xFF;

#if SPECIFY_TX_POWER
    fastPair_SetAdvTxPowerData(fp_disc_adv_data.tx_power_data, fastPair_GetTxPower());
#endif
#if (USE_EXTENDED_ADVERTS && SUPPORT_BLE_DEVICE_EXTENSION)
    fastPair_SetAdvCasData(fp_disc_adv_data.cas_data);
#endif
}

static void fastPair_InitNonDiscoverableAdvData(void)
{
    size_t i = 0;
    fp_non_disc_adv_data.account_data[i++] = SIZE_ACCOUNT_DATA_ADV - 1;
    fp_non_disc_adv_data.account_data[i++] = ble_ad_type_service_data;
    fp_non_disc_adv_data.account_data[i++] = FAST_PAIR_GFPS_IDENTIFIER & 0xFF;
    fp_non_disc_adv_data.account_data[i++] = (FAST_PAIR_GFPS_IDENTIFIER >> 8) & 0xFF;
    fp_non_disc_adv_data.account_data[i++] = FP_ADV_PAYLOAD_FLAGS;
    fp_non_disc_adv_data.account_data[i++] = FP_ADV_PAYLOAD_ACCOUNT_KEY_DATA;

#if SPECIFY_TX_POWER
    fastPair_SetAdvTxPowerData(fp_non_disc_adv_data.tx_power_data, fastPair_GetTxPower());
#endif
#if (USE_EXTENDED_ADVERTS && SUPPORT_BLE_DEVICE_EXTENSION)
    fastPair_SetAdvCasData(fp_non_disc_adv_data.cas_data);
#endif
}


Task fastPair_AdvGetAdvTask(void)
{
    return (&fastpair_advert.task);
}

#ifdef USE_SYNERGY
static void appHandleCmPrim(Message message)
{
    CsrBtCmPrim *prim = (CsrBtCmPrim *) message;

    switch (*prim)
    {

        case CSR_BT_CM_CRYPTO_HASH_CFM:
            DEBUG_LOG("FastPair_AdvHandleMessage appHandleCmPrim - CSR_BT_CM_CRYPTO_HASH_CFM");
            fastPair_AdvHandleHashCfm((CsrBtCmCryptoHashCfm *)message);
        break;

        default:
            DEBUG_LOG("FastPair_AdvHandleMessage appHandleCmPrim, unexpected CM prim 0x%04x", *prim);
            break;
    }

    CmFreeUpstreamMessageContents(message);
}
#endif /* USE_SYNERGY */

/*! \brief Message Handler

    This function is the main message handler for the fast pair advertising module.
*/
static void FastPair_AdvHandleMessage(Task task, MessageId id, Message message)
{
    UNUSED(task);

    switch (id)
    {
#ifdef USE_SYNERGY
        case CM_PRIM:
            appHandleCmPrim(message);
            break;
#endif /* USE_SYNERGY */

#ifndef USE_SYNERGY
        case CL_CRYPTO_HASH_CFM:
            fastPair_AdvHandleHashCfm((CL_CRYPTO_HASH_CFM_T *)message);
        break;
#endif /* !USE_SYNERGY */

        case FAST_PAIR_ADVERTISING_INTERNAL_RETAIN_RPA:
        {
            DEBUG_LOG_INFO("FastPair_AdvHandleMessage, FAST_PAIR_ADVERTISING_INTERNAL_RETAIN_RPA timeout, updating RPA");
            fastPair_AdvNotifyDataChange();
        }
        break;


        default:
            DEBUG_LOG("Unhandled MessageID = MSG:fast_pair_state_event_id:0x%d", id);
        break;
    }
}

/*! \brief Provide fastpair advert data when Identifiable (i.e. BR/EDR discoverable)

    Each data item in GetItems will be invoked separately by Adv Mgr, more precisely, one item per AD type.
*/
static inline le_adv_item_data_t fastPair_GetDataIdentifiable(void)
{
    DEBUG_LOG_FP_ADVERTISING("FP ADV: fastPair_GetDataIdentifiable: Model Id data item");
    fastPair_SetAdvTxPowerData(fp_disc_adv_data.tx_power_data, fastPair_GetTxPower());
    return fp_disc_adv;
}

/*! \brief Get the advert data for account key filter
*/
static le_adv_item_data_t fastPairGetAccountKeyFilterAdvData(bool compute_size_only)
{
    le_adv_item_data_t data_item;
    uint16 bloom_filter_size = fastPairGetBloomFilterLen();
    uint16 adv_size=0;

    if (fastpair_advert.account_key_filter_adv_data)
    {
        free(fastpair_advert.account_key_filter_adv_data);
        fastpair_advert.account_key_filter_adv_data=NULL;
    }

    if (bloom_filter_size)
    {
        /* calculate total size of advert data (max 32 bytes - doesn't fit in legacy advert)
            - service type and length (max 4)
            - bloom filter (max 14)
            - optionally battery notifications (may be 0 - max 4)
            - mandatory random resovable data (encrypted connection status field for SASS) (max 5)
            - optionally tx power (may be 0 - max 3)
            - CAS (Common Audio Service) UUID (max 4)
         */
        uint8_t gfps_adv_size = SIZE_GFPS_ID_ADV + bloom_filter_size + FP_BATTERY_NOTFICATION_SIZE + fastPair_SASSGetAdvDataSize();
        adv_size = gfps_adv_size + FP_TX_POWER_DATA_SIZE + FP_CAS_DATA_SIZE;

        if(!compute_size_only)
        {
            uint8 index = 0;
            fastpair_advert.account_key_filter_adv_data = PanicUnlessMalloc(adv_size);
            fastpair_advert.account_key_filter_adv_data[index++] = gfps_adv_size-FP_SIZE_LENGTH_FIELD;
            fastpair_advert.account_key_filter_adv_data[index++] = ble_ad_type_service_data;
            fastpair_advert.account_key_filter_adv_data[index++] = (uint8)(FAST_PAIR_GFPS_IDENTIFIER & 0xFF);
            fastpair_advert.account_key_filter_adv_data[index++] = (uint8)((FAST_PAIR_GFPS_IDENTIFIER >> 8) & 0xFF);
            memcpy(&fastpair_advert.account_key_filter_adv_data[index], fastPairGetBloomFilterData(), bloom_filter_size);
            index += bloom_filter_size;
            /* add optional battery state data if available */
            if (FP_BATTERY_NOTFICATION_SIZE)
            {
                memcpy(&fastpair_advert.account_key_filter_adv_data[index], FastPair_BatteryGetData(), FP_BATTERY_NOTFICATION_SIZE);
                index += FP_BATTERY_NOTFICATION_SIZE;
            }
            if(fastPair_SASSGetAdvDataSize())
            {
                /* Add mandatory Random Resolvable Data for SASS feature */
                memcpy(&fastpair_advert.account_key_filter_adv_data[index], fastPair_SASSGetAdvData(), fastPair_SASSGetAdvDataSize());
                index += fastPair_SASSGetAdvDataSize();
            }
#if FP_TX_POWER_DATA_SIZE 
            fastPair_SetAdvTxPowerData(&fastpair_advert.account_key_filter_adv_data[index], fastPair_GetTxPower());
            index += FP_TX_POWER_DATA_SIZE;
#endif
#if (USE_EXTENDED_ADVERTS && SUPPORT_BLE_DEVICE_EXTENSION)
            memcpy(&fastpair_advert.account_key_filter_adv_data[index], fp_cas_data, FP_CAS_DATA_SIZE);
            index += FP_CAS_DATA_SIZE;
#endif
            DEBUG_LOG_FP_ADVERTISING("fastPairGetAccountKeyFilterAdvData advert size=%d", adv_size);
            DEBUG_LOG_FP_ADVERTISING_DATA(fastpair_advert.account_key_filter_adv_data, adv_size);
        }
        else
        {
            DEBUG_LOG_FP_ADVERTISING("FP ADV: fastPairGetAccountKeyFilterAdvData: bloom_filter_size %d adv_size %d", bloom_filter_size, adv_size);
        }
    }

    data_item.size = adv_size;
    data_item.data = fastpair_advert.account_key_filter_adv_data;

    return data_item;
}

/*! \brief Provide fastpair advert data when Unidentifiable (i.e. BR/EDR non-discoverable)

    Each data item in GetItems will be invoked separately by Adv Mgr, more precisely, one item per AD type.

    \param  compute_size_only TRUE means the caller is only interested in the size of data, so avoid any
            allocating memory for actual data
*/
static le_adv_item_data_t fastPair_GetDataUnIdentifiable(bool compute_size_only)
{
    le_adv_item_data_t data_item={0};
    if(!UserAccounts_GetNumAccountKeys())
    {
        DEBUG_LOG_FP_ADVERTISING("FP ADV: fastPair_GetDataUnIdentifiable: GFPS Id data item with empty Account Key");

        fastPair_SetAdvTxPowerData(fp_non_disc_adv_data.tx_power_data, fastPair_GetTxPower());
        data_item = fp_non_disc_adv;
    }
    else
    {
        DEBUG_LOG_FP_ADVERTISING("FP ADV: fastPair_GetDataUnIdentifiable: GFPS Id data item with Account Key");
        data_item = fastPairGetAccountKeyFilterAdvData(compute_size_only);
        fastpair_advert.in_use_account_key_active = FastPair_SASSGetInUseAccountKeyActiveFlag();
#ifdef USE_FAST_PAIR_ACCOUNT_DATA_V0
        /*Generate new bloom filter and keep it ready for advertisements in BR/EDR Connectable and non-discoverable mode
        This will ensure next callback would have new Salt*/
        DEBUG_LOG("FP ADV: fastPair_GetDataUnIdentifiable: FastPair_GenerateBloomFilter\n");
        fastPairTaskData * theFastPair = fastPair_GetTaskData();
        /* Generating Bloom filter only In Idle state(fresh pair) and Wait Account Key state(Subsequent pair)*/
        if((fastPair_GetState(theFastPair) == FAST_PAIR_STATE_IDLE) || (fastPair_GetState(theFastPair) == FAST_PAIR_STATE_WAIT_ACCOUNT_KEY))
        {
            FastPair_GenerateBloomFilter();
        }
#endif
    }
    return data_item;
}

static inline void fastPair_ReleaseAdvertisingDataItem(void)
{
    if (fastpair_advert.account_key_filter_adv_data)
    {
        free(fastpair_advert.account_key_filter_adv_data);
        fastpair_advert.account_key_filter_adv_data=NULL;
    }
}

static inline bool fastPair_ShouldAdvertiseIdentifiable(void)
{
    return fastpair_advert.identifiable;
}

static unsigned fastPair_GetItemDataSize(void)
{
    return fastPair_ShouldAdvertiseIdentifiable()
            ? fastPair_GetDataIdentifiable().size
                : fastPair_GetDataUnIdentifiable(TRUE).size;
}

static bool fastPair_GetItemData(le_adv_item_data_t * data)
{
    PanicNull(data);
    data->data = NULL;
    data->size = 0;

    *data = fastPair_ShouldAdvertiseIdentifiable()
            ? fastPair_GetDataIdentifiable()
                : fastPair_GetDataUnIdentifiable(FALSE);
    return TRUE;
}

static void fastPair_ReleaseItemData(void)
{
    fastPair_ReleaseAdvertisingDataItem();
}

static inline le_adv_data_type_t fastPair_GetAdvType(void)
{
#if USE_EXTENDED_ADVERTS
    return le_adv_type_extended_connectable;
#else
    return le_adv_type_legacy_connectable_scannable;
#endif
}

static bool fastPair_GetItemInfo(le_adv_item_info_t * info)
{
    PanicNull(info);
    *info = (le_adv_item_info_t){ .placement = le_adv_item_data_placement_advert,
                                        .type = fastPair_GetAdvType(),
                                        .data_size = fastPair_GetItemDataSize()};
    if(fp_adv_param_tx_power.specify_max_tx_power)
    {
        info->needs_tx_power = TRUE;
    }

    return TRUE;
}

static bool fastPair_GetItemParams(le_adv_item_params_t * params)
{
    PanicNull(params);
    bdaddr mru_adv_addr = fastPair_AdvGetBdaddr();

    BdaddrTypedSetEmpty(&params->random_addr);

    if(FastPair_GetConfig()->support_ble_device_extension && fastPair_ShouldAdvertiseIdentifiable())
    {
       params->own_addr_type = OWN_ADDRESS_PUBLIC;
    }
    else
    {
        if (MessagePendingFirst(fastPair_AdvGetAdvTask(), FAST_PAIR_ADVERTISING_INTERNAL_RETAIN_RPA, NULL) &&
            !BdaddrIsZero(&mru_adv_addr))
        {
            params->random_addr_type = ble_local_addr_write_resolvable;
            params->random_addr.type = TYPED_BDADDR_RANDOM;
            params->random_addr.addr = mru_adv_addr;
        }
        else
        {
            params->random_addr_type = ble_local_addr_generate_resolvable;
        }
        params->own_addr_type = OWN_ADDRESS_RANDOM;

        params->random_addr_generate_rotation_timeout_minimum_in_minutes = 14;
        params->random_addr_generate_rotation_timeout_maximum_in_minutes = 15;
    }

    DEBUG_LOG_INFO("fastPair_GetItemParams, random=%u, enum:ble_local_addr_type:%u, lap=%06lx", params->own_addr_type,
                                                        params->random_addr_type, params->random_addr.addr.lap);

    if(fastPair_ShouldAdvertiseIdentifiable())
    {
        params->primary_adv_interval_max = MSEC_TO_LE_TIMESLOT(FP_ADV_INTERVAL_IDENTIFIABLE_MAX);
        params->primary_adv_interval_min = MSEC_TO_LE_TIMESLOT(FP_ADV_INTERVAL_IDENTIFIABLE_MIN);
    }
    else
    {
        params->primary_adv_interval_max = MSEC_TO_LE_TIMESLOT(FP_ADV_INTERVAL_UNIDENTIFIABLE_MAX);
        params->primary_adv_interval_min = MSEC_TO_LE_TIMESLOT(FP_ADV_INTERVAL_UNIDENTIFIABLE_MIN);
    }

    if(fp_adv_param_tx_power.specify_max_tx_power)
    {
        params->max_tx_power = fp_adv_param_tx_power.max_tx_power;
    }
    return TRUE;
}

#if (!USE_EXTENDED_ADVERTS && SUPPORT_BLE_DEVICE_EXTENSION)
static bool fastPair_GetCASItemData(le_adv_item_data_t * data)
{
    PanicNull(data);
    *data = fp_cas_data_item;
    return TRUE;
}

static bool fastPair_GetCASItemInfo(le_adv_item_info_t * info)
{
    PanicNull(info);
    *info = (le_adv_item_info_t){ .placement = le_adv_item_data_placement_scan_response,
                                        .type = le_adv_type_legacy_connectable_scannable,
                                        .data_size = FP_CAS_ADV_ITEM_DATA_SIZE};
    return TRUE;
}

static const le_adv_item_callback_t fastPair_CAS_advertising_callback = {
    .GetItemData = &fastPair_GetCASItemData,
    .GetItemInfo = &fastPair_GetCASItemInfo,
    .GetItemParameters = &fastPair_GetItemParams,
};
#endif

static void fastPair_HandleAdvertisingEventNotified(le_adv_event_t event, const void * event_data)
{
    DEBUG_LOG_FP_ADVERTISING("FP ADV: fastPair_HandleAdvertisingEvent event ID enum:le_adv_event_t:%d", event);

    if(event == LEAM_EVENT_ADVERTISING_SET_SUSPENDED)
    {
#ifdef USE_FAST_PAIR_ACCOUNT_DATA_V0
        fastPair_AdvNotifyDataChange();
#else
        // RPA will change when the set is next enabled so recalculate the bloom filter in 
        // anticipation of this
        if(!FastPair_GenerateBloomFilter())
        {
            /* Kick advertising manager to restart advertising with new RPA immediately */
            fastPair_AdvNotifyDataChange();
        }
#endif
    }
    else if (event == LEAM_EVENT_ADVERTISING_SET_RANDOM_ADDRESS_CHANGED)
    {
        const LEAM_EVENT_ADVERTISING_SET_RANDOM_ADDRESS_CHANGED_T * data = (const LEAM_EVENT_ADVERTISING_SET_RANDOM_ADDRESS_CHANGED_T *)event_data;

        DEBUG_LOG_FP_ADVERTISING("fastPair_HandleAdvertisingEventNotified: new addr LAP: 0x%06lx", data->new_bdaddr.lap);
        fastpair_advert.adv_bdaddr = data->new_bdaddr;
    }
}

static const le_adv_item_callback_t fastPair_advertising_callback = {
    .GetItemData = &fastPair_GetItemData,
    .ReleaseItemData = &fastPair_ReleaseItemData,
    .GetItemParameters = &fastPair_GetItemParams,
    .NotifyAdvertisingEvent = &fastPair_HandleAdvertisingEventNotified,
    .GetItemInfo = &fastPair_GetItemInfo

};

/*! \brief Function to initialise the fastpair advertising globals
*/
static void fastPair_InitialiseAdvGlobal(void)
{
    memset(&fastpair_advert, 0, sizeof(fastpair_advert_data_t));
    fastpair_advert.in_use_account_key_active = FALSE;
    fp_adv_param_tx_power.specify_max_tx_power = FALSE;
    fp_adv_param_tx_power.max_tx_power = 0;

}

/*! @brief Private API to initialise fastpair
 */
bool fastPair_SetUpAdvertising(void)
{
    DEBUG_LOG_FP_ADVERTISING("FP ADV: fastPair_SetUpAdvertising");
    /*Initialise fastpair advertising globals*/
    fastPair_InitialiseAdvGlobal();

    /* Initialise Fast Pair SASS advertising module */
    fastPair_SetUpSASSAdvertising();

    /*Mandate use of Transmit Power in fastpair adverts*/
    TxPower_Mandatory(TRUE, le_client_fast_pair);

    /* Initialise discoverable advertising data */
    fastPair_InitDiscoverableAdvData();

    /* Initialise undiscoverable advertising data */
    fastPair_InitNonDiscoverableAdvData();

    /*Initialise fastpair bloom filter globals*/
    fastPair_InitBloomFilter();

    /* Set up task handler */
    fastPair_AdvGetAdvTask()->handler = FastPair_AdvHandleMessage;

    return TRUE;
}

/*! @brief Private API to handle change in Connectable state and notify the LE Advertising Manager
 */
bool fastPair_AdvNotifyChangeInConnectableState(bool connectable)
{
    if(IsHandsetConnAllowed != connectable)
    {
        IsHandsetConnAllowed = connectable;
        DEBUG_LOG_FP_ADVERTISING("fastPair_AdvNotifyChangeInConnectableState %d", connectable);
            
        if(connectable && fastpair_advert.adv_register_handle == NULL)
        {
            fastpair_advert.adv_register_handle = LeAdvertisingManager_RegisterAdvertisingItemCallback(NULL, &fastPair_advertising_callback);
#if (!USE_EXTENDED_ADVERTS && SUPPORT_BLE_DEVICE_EXTENSION)
            /* When using legacy adverts CAS UUID doesn't fit the GFPS adv item. */
            fastpair_advert.cas_register_handle = LeAdvertisingManager_RegisterAdvertisingItemCallback(NULL, &fastPair_CAS_advertising_callback);
#endif
        }
        else
        {
            fastPair_AdvNotifyDataChange();
        }
    }
    return TRUE;
}

void fastPair_AdvNotifyPairingSuccess(void)
{
    DEBUG_LOG_INFO("fastPair_AdvNotifyPairingSuccess, retain RPA for %u ms", FAST_PAIR_ADVERTISING_RETAIN_RPA_TIMEOUT_MS);

    MessageCancelAll(fastPair_AdvGetAdvTask(), FAST_PAIR_ADVERTISING_INTERNAL_RETAIN_RPA);
    MessageSendLater(fastPair_AdvGetAdvTask(), FAST_PAIR_ADVERTISING_INTERNAL_RETAIN_RPA, NULL, FAST_PAIR_ADVERTISING_RETAIN_RPA_TIMEOUT_MS);
}


bool fastPair_AdvNotifyChangeInIdentifiable(bool identifiable)
{
    bool notify = FALSE;
    if(fastpair_advert.identifiable != identifiable)
    {
        fastpair_advert.identifiable = identifiable;
        DEBUG_LOG_FP_ADVERTISING("fastPair_AdvNotifyChangeInIdentifiable %d", fastpair_advert.identifiable);
        notify = fastPair_AdvNotifyDataChange();
        MessageCancelAll(fastPair_AdvGetAdvTask(), FAST_PAIR_ADVERTISING_INTERNAL_RETAIN_RPA);
        
        /* Upon exiting identifiable/pairing mode, use the same RPA for a while afterwards */
        if (!identifiable)
        {
            DEBUG_LOG_INFO("fastPair_AdvNotifyChangeInIdentifiable, retain RPA for %u ms", FAST_PAIR_ADVERTISING_RETAIN_RPA_TIMEOUT_MS);
            MessageSendLater(fastPair_AdvGetAdvTask(), FAST_PAIR_ADVERTISING_INTERNAL_RETAIN_RPA, NULL, FAST_PAIR_ADVERTISING_RETAIN_RPA_TIMEOUT_MS);
        }

    }
    return notify;
}

/*! @brief Private API to notify the LE Advertising Manager on FP adverts data change
 */
bool fastPair_AdvNotifyDataChange(void)
{
    bool status = FALSE;
    if (fastpair_advert.adv_register_handle)
    {
        status = LeAdvertisingManager_UpdateAdvertisingItem(fastpair_advert.adv_register_handle);
#if (!USE_EXTENDED_ADVERTS && SUPPORT_BLE_DEVICE_EXTENSION)
        if(status && fastpair_advert.cas_register_handle)
        {
            LeAdvertisingManager_UpdateAdvertisingItem(fastpair_advert.cas_register_handle);
        }
#endif
    }
    else
    {
        DEBUG_LOG_FP_ADVERTISING("FP ADV: Invalid handle in fastPair_AdvNotifyDataChange");
    }
    return status;
}

/*! @brief Private API to provide BR/EDR discoverablity information
 */
bool fastPair_AdvIsBrEdrDiscoverable(void)
{
    DEBUG_LOG_FP_ADVERTISING("FP ADV: fastpair_AdvIsBrEdrDiscoverable %d", fastpair_advert.identifiable);
    return fastpair_advert.identifiable;
}

bool fastPair_AdvInUseAccountKeyActiveState(void)
{
    return fastpair_advert.in_use_account_key_active;
}

bdaddr fastPair_AdvGetBdaddr(void)
{
    return fastpair_advert.adv_bdaddr;
}

void fastPair_AdvSpecifyMaxTxPower(int8_t max_tx_power)
{
#if SPECIFY_TX_POWER
    fp_adv_param_tx_power.specify_max_tx_power = TRUE;
    fp_adv_param_tx_power.max_tx_power = max_tx_power;
    fastPair_AdvNotifyDataChange();
#else
    UNUSED(max_tx_power);
    DEBUG_LOG_ERROR("fastPair_AdvSpecifyMaxTxPower called but fast pair advertising is not configured to specify it's own TX Power");
#endif
}
